package com.geeguo.ebuilder.core.common.utils;

import org.apache.commons.lang3.ClassUtils;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public abstract class ObjectEqualsHelper {
    
	private static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("0.00000");
	private static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private static final Set<String> CAN_EQUALS_TYPES = new HashSet<String>();
	private static final ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

	static {
		CAN_EQUALS_TYPES.add(String.class.getSimpleName());
        CAN_EQUALS_TYPES.add(Boolean.class.getSimpleName());
        CAN_EQUALS_TYPES.add(boolean.class.getSimpleName());
        CAN_EQUALS_TYPES.add(Long.class.getSimpleName());
        CAN_EQUALS_TYPES.add(long.class.getSimpleName());
		CAN_EQUALS_TYPES.add(Integer.class.getSimpleName());
		CAN_EQUALS_TYPES.add(int.class.getSimpleName());
		CAN_EQUALS_TYPES.add(Short.class.getSimpleName());
		CAN_EQUALS_TYPES.add(short.class.getSimpleName());
		CAN_EQUALS_TYPES.add(Byte.class.getSimpleName());
		CAN_EQUALS_TYPES.add(byte.class.getSimpleName());
		CAN_EQUALS_TYPES.add(Character.class.getSimpleName());
		CAN_EQUALS_TYPES.add(char.class.getSimpleName());
        CAN_EQUALS_TYPES.add(BigDecimal.class.getSimpleName());
	}
	
    public static boolean isSimpleValueType(Class<?> clazz) {
        return ClassUtils.isPrimitiveOrWrapper(clazz)
                || CharSequence.class.isAssignableFrom(clazz)
                || Date.class.isAssignableFrom(clazz)
                || Number.class.isAssignableFrom(clazz);
    }
	
    public static <T> Map<String, Object> equals(T target, T old) {
        Map<String, Object> changeValues = new HashMap<String, Object>();
        MetaClass metaClzz = MetaClass.forClass(target.getClass(), reflectorFactory);
        String[] readablePropertyNames = metaClzz.getGetterNames();
        if (readablePropertyNames != null && readablePropertyNames.length > 0) {
            for (String propertyName : readablePropertyNames) {
                Class getterType = metaClzz.getGetterType(propertyName);
                if (isSimpleValueType(getterType)) {
                    try {
                        Invoker invoker = metaClzz.getGetInvoker(propertyName);
                        Object targetPropVal = invoker.invoke(target, (Object[]) null);
                        Object oldPropVal = invoker.invoke(old, (Object[]) null);
                        if (CAN_EQUALS_TYPES.contains(getterType.getSimpleName())) {
                            if (objectEquals(targetPropVal, oldPropVal)) {
                                continue;
                            }
                            changeValues.put(propertyName, targetPropVal);
                        } else if ((getterType == double.class || getterType == Double.class)) {
                            if (doubleEquals((Double) targetPropVal, (Double) oldPropVal)) {
                                continue;
                            }
                            changeValues.put(propertyName, targetPropVal);
                        } else if ((getterType == float.class || getterType == Float.class)) {
                            if (floatEquals((Float) targetPropVal, (Float) oldPropVal)) {
                                continue;
                            }
                            changeValues.put(propertyName, targetPropVal);
                        } else if (Date.class.isAssignableFrom(getterType) && !dateEquals((Date) targetPropVal, (Date) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return changeValues;
    }
    
    public static <T> Map<String, Object> equals(T target, T old, String[] readablePropertyNames) {
        Map<String, Object> changeValues = new HashMap<String, Object>();
        MetaClass metaClzz = MetaClass.forClass(target.getClass(), reflectorFactory);
        if (readablePropertyNames != null && readablePropertyNames.length > 0) {
            for (String propertyName : readablePropertyNames) {
                if (!metaClzz.hasGetter(propertyName)) {
                    continue;
                }
                Class getterType = metaClzz.getGetterType(propertyName);
                if (isSimpleValueType(getterType)) {
                    try {
                        Object targetPropVal = metaClzz.getGetInvoker(propertyName).invoke(target, (Object[]) null);
                        Object oldPropVal = metaClzz.getGetInvoker(propertyName).invoke(old, (Object[]) null);
                        if (CAN_EQUALS_TYPES.contains(getterType.getSimpleName()) && !objectEquals(targetPropVal, oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        }
                        if ((getterType == double.class || getterType == Double.class) && !doubleEquals((Double) targetPropVal, (Double) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if ((getterType == float.class || getterType == Float.class) && !floatEquals((Float) targetPropVal, (Float) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if (Date.class.isAssignableFrom(getterType) && !dateEquals((Date) targetPropVal, (Date) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                        }
                    } catch (Exception ex) {
                        
                    }
                }
            }
        }
        return changeValues;
    }
    
    public static <T> Map<String, Object> equals(T target, T old, String numberFormat, String dateFormate, String[] readablePropertyNames) {
        Map<String, Object> changeValues = new HashMap<String, Object>();
        MetaClass metaClzz = MetaClass.forClass(target.getClass(), reflectorFactory);
        if (readablePropertyNames != null && readablePropertyNames.length > 0) {
            for (String propertyName : readablePropertyNames) {
                if (!metaClzz.hasGetter(propertyName)) {
                    continue;
                }
                Class getterType = metaClzz.getGetterType(propertyName);
                if (isSimpleValueType(getterType)) {
                    try {
                        Object targetPropVal = metaClzz.getGetInvoker(propertyName).invoke(target, (Object[]) null);
                        Object oldPropVal = metaClzz.getGetInvoker(propertyName).invoke(old, (Object[]) null);
                        if (CAN_EQUALS_TYPES.contains(getterType.getSimpleName()) && !objectEquals(targetPropVal, oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        }
                        if ((getterType == double.class || getterType == Double.class) && !doubleEquals((Double) targetPropVal, (Double) oldPropVal,numberFormat)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if ((getterType == float.class || getterType == Float.class) && !floatEquals((Float) targetPropVal, (Float) oldPropVal,numberFormat)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if (Date.class.isAssignableFrom(getterType) && !dateEquals((Date) targetPropVal, (Date) oldPropVal,dateFormate)) {
                            changeValues.put(propertyName, targetPropVal);
                        }
                    } catch (Exception ex) {
                        
                    }
                }
            }
        }
        return changeValues;
    }
    
    public static <T> Map<String, Object> equalsWithDatePattern(T target, T old, String dateFormate, String[] readablePropertyNames) {
        Map<String, Object> changeValues = new HashMap<String, Object>();
        MetaClass metaClzz = MetaClass.forClass(target.getClass(), reflectorFactory);
        if (readablePropertyNames != null && readablePropertyNames.length > 0) {
            for (String propertyName : readablePropertyNames) {
                if (!metaClzz.hasGetter(propertyName)) {
                    continue;
                }
                Class getterType = metaClzz.getGetterType(propertyName);
                if (isSimpleValueType(getterType)) {
                    try {
                        Object targetPropVal = metaClzz.getGetInvoker(propertyName).invoke(target, (Object[]) null);
                        Object oldPropVal = metaClzz.getGetInvoker(propertyName).invoke(old, (Object[]) null);
                        if (CAN_EQUALS_TYPES.contains(getterType.getSimpleName()) && !objectEquals(targetPropVal, oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        }
                        if ((getterType == double.class || getterType == Double.class) && !doubleEquals((Double) targetPropVal, (Double) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if ((getterType == float.class || getterType == Float.class) && !floatEquals((Float) targetPropVal, (Float) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if (Date.class.isAssignableFrom(getterType) && !dateEquals((Date) targetPropVal, (Date) oldPropVal,dateFormate)) {
                            changeValues.put(propertyName, targetPropVal);
                        }
                    } catch (Exception ex) {
                        
                    }
                }
            }
        }
        return changeValues;
    }
    
    public static <T> Map<String, Object> equalsWithNumberPattern(T target, T old, String numberFormat, String[] readablePropertyNames) {
        Map<String, Object> changeValues = new HashMap<String, Object>();
        MetaClass metaClzz = MetaClass.forClass(target.getClass(), reflectorFactory);
        if (readablePropertyNames != null && readablePropertyNames.length > 0) {
            for (String propertyName : readablePropertyNames) {
                if (!metaClzz.hasGetter(propertyName)) {
                    continue;
                }
                Class getterType = metaClzz.getGetterType(propertyName);
                if (isSimpleValueType(getterType)) {
                    try {
                        Object targetPropVal = metaClzz.getGetInvoker(propertyName).invoke(target, (Object[]) null);
                        Object oldPropVal = metaClzz.getGetInvoker(propertyName).invoke(old, (Object[]) null);
                        if (CAN_EQUALS_TYPES.contains(getterType.getSimpleName()) && !objectEquals(targetPropVal, oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        }
                        if ((getterType == double.class || getterType == Double.class) && !doubleEquals((Double) targetPropVal, (Double) oldPropVal,numberFormat)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if ((getterType == float.class || getterType == Float.class) && !floatEquals((Float) targetPropVal, (Float) oldPropVal,numberFormat)) {
                            changeValues.put(propertyName, targetPropVal);
                            continue;
                        } else if (Date.class.isAssignableFrom(getterType) && !dateEquals((Date) targetPropVal, (Date) oldPropVal)) {
                            changeValues.put(propertyName, targetPropVal);
                        }
                    } catch (Exception ex) {
                        
                    }
                }
            }
        }
        return changeValues;
    }
    
	public static <T> boolean objectEquals(T one, T another) {
	    if (one == another) {
	        return true;
        }	  
	    if (one == null || another == null) {
	        return false;
        }		  
	    return one.equals(another);
	}
	
    public static boolean dateEquals(Date one, Date another, String format) {
		if (one == another) {
		    return true;
	    }		  
	    if (one == null || another == null) {
	        return false;
        }
	    if (format != null) {
	    	SimpleDateFormat sdf = new SimpleDateFormat(format);
	  	    String oneVal = sdf.format(one);
	  	    String twoVal = sdf.format(another);		
	  	    return oneVal.equals(twoVal);
	    }
	    String oneVal = DEFAULT_DATE_FORMAT.format(one);
	    String twoVal = DEFAULT_DATE_FORMAT.format(another);		
	    return oneVal.equals(twoVal);
	}

    public static boolean dateEquals(Date one, Date another) {
        if (one == another) {
            return true;
        }
        if (one == null || another == null) {
            return false;
        }
        String oneVal = DEFAULT_DATE_FORMAT.format(one);
        String twoVal = DEFAULT_DATE_FORMAT.format(another);
        return oneVal.equals(twoVal);
    }
	
	public static boolean doubleEquals(Double one, Double another, String format) {
		if (one == another) {
		    return true;
	    }
	    if (one == null || another == null) {
	        return false;
        }
	    return isDoubleEquals(one, another, format);
	}
	
	public static boolean doubleEquals(Double one, Double another) {
		if (one == another) {
		    return true;
		}
	    if (one == null || another == null) {
	        return false;
	    }
	    return isDoubleEquals(one,another);		
	}
	
	public static boolean isDoubleEquals(double one, double another, String format) {
		if (format != null) {
			DecimalFormat df = new DecimalFormat(format);
			String oneVal = df.format(one);
			String twoVal = df.format(another);
			return oneVal.equals(twoVal);
		}
		return isDoubleEquals(one,another);		
	}
	
	public static boolean isDoubleEquals(double one, double another) {
		String oneVal = DEFAULT_DECIMAL_FORMAT.format(one);
		String twoVal = DEFAULT_DECIMAL_FORMAT.format(another);
		return oneVal.equals(twoVal);
	}
	
	public static boolean floatEquals(Float one, Float another, String format) {
        if (one == another) {
            return true;
        }
        if (one == null || another == null) {
            return false;
        }
        return isFloatEquals(one, another, format);
    }
    
    public static boolean floatEquals(Float one, Float another) {
        if (one == another) {
            return true;
        }
        if (one == null || another == null) {
            return false;
        }
        return isFloatEquals(one,another);
    }
    
    public static boolean isFloatEquals(float one, float another, String format) {
    	if (format != null) {
			DecimalFormat df = new DecimalFormat(format);
			String oneVal = df.format(one);
			String twoVal = df.format(another);
			return oneVal.equals(twoVal);
		}
		return isFloatEquals(one,another);		
    }
    
    public static boolean isFloatEquals(double one, double another) {
        String oneVal = DEFAULT_DECIMAL_FORMAT.format(one);
        String twoVal = DEFAULT_DECIMAL_FORMAT.format(another);
        return oneVal.equals(twoVal);
    }
}